#include "mut.h"
#include <iostream>
#include <fstream>
using namespace std;
ofstream fout("log.txt");

void MUTLog( char* str ){
	fout<<str<<endl;
}

MUTSTATE g_MutState;
ONCREATEDEVICE g_OnCreateDevice = NULL;
ONRESETDEVICE g_OnResetDevice = NULL;
ONRELEASEDEVICE g_OnReleaseDevice = NULL;
ONRENDER g_OnRender = NULL;
ONWNDPROC g_OnWndProc = NULL;

HRESULT MUTSetOnCreateDevice(ONCREATEDEVICE func){
	g_OnCreateDevice = func;
	return S_OK;
}
HRESULT MUTSetOnResetDevice(ONRESETDEVICE func){
	g_OnResetDevice = func;
	return S_OK;
}
HRESULT MUTSetOnReleaseDevice(ONRELEASEDEVICE func){
	g_OnReleaseDevice = func;
	return S_OK;
}
HRESULT MUTSetOnRender(ONRENDER func){
	g_OnRender = func;
	return S_OK;
}
HRESULT MUTSetOnWndProc(ONWNDPROC func){
	g_OnWndProc = func;
	return S_OK;
}

MUTSTATE* MUTState(){
	return &g_MutState;
}

HRESULT MUTCreateWindow(HINSTANCE hInst, TString strWindowName, int iWidth, int iHeight, bool bWindowed){
	DWORD dwStyle = MUTWS_WINDOWED;
	DWORD dwStyleEx = MUTWS_EX_WINDOWED;
	bool bWindowedFlag = bWindowed;

	if (!bWindowedFlag){
		dwStyle = MUTWS_FULLSCREEN;
		dwStyleEx = MUTWS_EX_FULLSCREEN;
	}
	MUTState()->SetStyle(dwStyle);
	MUTState()->SetStyleEx(dwStyleEx);
	MUTState()->SetWindowed(bWindowedFlag);

	RECT rtWnd = {0,0,iWidth,iHeight};
	AdjustWindowRectEx(&rtWnd,dwStyle,FALSE,dwStyleEx);
	MUTState()->SetWidth(iWidth);
	MUTState()->SetHeight(iHeight);

	WNDCLASS wc;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hbrBackground = 0;
	wc.hCursor = LoadCursor(NULL,IDC_ARROW);
	wc.hIcon = LoadIcon(NULL,IDI_APPLICATION);
	wc.hInstance = hInst;
	wc.lpfnWndProc = MUTWndProc;
	wc.lpszClassName = L"MutClass";
	wc.lpszMenuName = NULL;
	wc.style = CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS|CS_OWNDC;

	if (!RegisterClass(&wc)){
		MUTWinErrorTrace(GetLastError());
		return E_FAIL;
	}	

	HWND hwnd = CreateWindowEx(MUTState()->GetStyleEx(),
				L"MutClass",
				strWindowName,
				MUTState()->GetStyle(),
				CW_USEDEFAULT, CW_USEDEFAULT,
				rtWnd.right-rtWnd.left, 
				rtWnd.bottom-rtWnd.top,
				NULL, NULL, hInst, NULL);

	if (!hwnd){
		MUTWinErrorTrace(GetLastError());
		return E_FAIL;
	}

	ShowWindow(hwnd,SW_NORMAL);

	MUTState()->Sethwnd(hwnd);
	MUTState()->SetInstance(hInst);
	MUTState()->SetD3DPP_hDeviceWindow(hwnd);

	MUTRequestSetup();
	return S_OK;
}

HRESULT MUTD3DCreate9(){
	LPDIRECT3D9 pD3D = Direct3DCreate9(D3D_SDK_VERSION);
	if (pD3D == NULL){
		return E_FAIL;
	}
	MUTState()->SetD3DObj(pD3D);
	MUTState()->SetDevice(NULL);

	//Default settings
	MUTState()->SetDevType(D3DDEVTYPE_HAL);
	MUTState()->SetWindowed(TRUE);
	MUTState()->SetD3DPP_Windowed(TRUE);
	MUTState()->SetD3DPP_BackBufferCount(1);
	MUTState()->SetD3DPP_BackBufferWidth(GetSystemMetrics(SM_CXSCREEN));
	MUTState()->SetD3DPP_BackBufferHeight(GetSystemMetrics(SM_CYSCREEN));
	MUTState()->SetD3DPP_EnableAutoDepthStencil(TRUE);
	MUTState()->SetD3DPP_SwapEffect(D3DSWAPEFFECT_DISCARD);
	MUTState()->SetD3DPP_BackBufferFormat(D3DFMT_A8R8G8B8);
	MUTState()->SetD3DPP_AutoDepthStencilFormat(D3DFMT_D24S8);
	MUTState()->SetD3DPP_MultiSampleType(D3DMULTISAMPLE_NONE);
	MUTState()->SetD3DPP_MultiSampleQuality(0);
	MUTState()->SetVertexProcessing(D3DCREATE_HARDWARE_VERTEXPROCESSING);
	MUTState()->SetD3DPP_PresentationInterval(D3DPRESENT_INTERVAL_IMMEDIATE);
	return S_OK;
}

HRESULT MUTCreateDevice(){	
	LPDIRECT3D9 pD3D = MUTState()->GetD3DObj();
	if (!pD3D) return E_FAIL;

	D3DPRESENT_PARAMETERS* pD3DPP = MUTState()->GetD3DPP();
	LPDIRECT3DDEVICE9 pDevice = NULL;

	HRESULT hr = pD3D->CreateDevice(D3DADAPTER_DEFAULT, 
		D3DDEVTYPE_HAL, 
		MUTState()->Gethwnd(),
		D3DCREATE_HARDWARE_VERTEXPROCESSING,
		pD3DPP,
		&pDevice);
	if ( hr != D3D_OK ){
		switch( hr ){
		case D3DERR_DEVICELOST:
			break;
		case D3DERR_INVALIDCALL:
			break;
		case D3DERR_NOTAVAILABLE:
			break;
		case D3DERR_OUTOFVIDEOMEMORY:
			break;
		}
		MUTDXErrorTrace(hr,L"CreateDevice failed.");
		return E_FAIL;
	}
	MUTState()->SetDevice(pDevice);
	hr = S_OK;
	if (g_OnCreateDevice){
		hr = (*g_OnCreateDevice)(pDevice);
		if (FAILED(hr)){
			MUTDXErrorTrace(hr,L"OnCreateDevice failed.");
			return hr;
		}
	}
	if (g_OnResetDevice){
		hr = (*g_OnResetDevice)(pDevice);
		if (FAILED(hr)){
			MUTDXErrorTrace(hr,L"OnResetDevice failed.");
			return hr;
		}
	}
	return S_OK;
}

HRESULT MUTD3DRelease(){
	if (g_OnReleaseDevice){
		(*g_OnReleaseDevice)();
	}
	LPDIRECT3DDEVICE9 pDevice = MUTState()->GetDevice();
	LPDIRECT3D9 pD3D = MUTState()->GetD3DObj();
	if (pDevice) pDevice->Release();
	if (pD3D) pD3D->Release();
	MUTState()->SetD3DObj(NULL);
	MUTState()->SetDevice(NULL);
	return S_OK;
}

HRESULT MUTResetDevice(){
	LPDIRECT3DDEVICE9 pDevice = MUTState()->GetDevice();
	if ( !pDevice ) 
		return E_FAIL;
	HRESULT hr = pDevice->Reset(MUTState()->GetD3DPP());
	if (SUCCEEDED(hr)){
		if (g_OnResetDevice){
			hr = (*g_OnResetDevice)(pDevice);
			if (FAILED(hr)){
				MUTDXErrorTrace(hr,L"OnResetDevice failed.");
				return hr;
			}
		}
	}
	return hr;
}

HRESULT MUTD3DMain(){
	HRESULT hr = S_OK;

	LPDIRECT3DDEVICE9 pDevice = MUTState()->GetDevice();
	if (!pDevice){
		if (FAILED(MUTCreateDevice()))
			return E_FAIL;
		return S_OK;
	}
	hr = pDevice->TestCooperativeLevel();
	if ( hr != D3D_OK ){
		if ( hr == D3DERR_DEVICELOST){
			Sleep(50);
		}else if ( hr == D3DERR_DEVICENOTRESET ){
			MUTResetDevice();
		}
		return hr;
	}
	CheckThreadRequest();
	if (g_OnRender){
		(*g_OnRender)(pDevice);
	}
	return S_OK;
}

HRESULT MUTMainLoop(){
	MSG msg;
	msg.message = WM_NULL;
	MUTState()->SetElapsedTime(0.1f);
	while (msg.message != WM_QUIT){
		if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)){
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}else{
			INT64 freq,cntbegin,cntend;
			QueryPerformanceFrequency((LARGE_INTEGER*)&freq);
			QueryPerformanceCounter((LARGE_INTEGER*)&cntbegin);
			MUTD3DMain();
			QueryPerformanceCounter((LARGE_INTEGER*)&cntend);
			MUTState()->SetElapsedTime(float(cntend-cntbegin)/freq);
		}
	}
	MUTD3DRelease();
	return S_OK;
}

HRESULT MUTToggleFullscreen(){
	HWND hwnd = MUTState()->Gethwnd();
	if (MUTState()->GetWindowed()){
		//Change to fullscreen mode
		SetWindowLongPtr(hwnd,GWL_STYLE,MUTWS_FULLSCREEN);
		SetWindowLongPtr(hwnd,GWL_EXSTYLE,MUTWS_EX_FULLSCREEN);
		SetWindowPos(hwnd,HWND_TOPMOST,0,0,MUTState()->GetWidth(),MUTState()->GetHeight(),SWP_SHOWWINDOW);
		MUTState()->SetWindowed( FALSE );
		MUTState()->SetD3DPP_Windowed( FALSE );
	}else{
		//Change to windowed mode
		SetWindowLongPtr(hwnd,GWL_STYLE,MUTWS_WINDOWED);
		SetWindowLongPtr(hwnd,GWL_EXSTYLE,MUTWS_EX_WINDOWED);
		RECT rtWnd={0,0,MUTState()->GetWidth(),MUTState()->GetHeight()};
		AdjustWindowRectEx(&rtWnd, MUTWS_WINDOWED, FALSE, MUTWS_EX_WINDOWED);
		SetWindowPos(hwnd,HWND_TOPMOST,0,0,rtWnd.right,rtWnd.bottom,SWP_SHOWWINDOW);
		MUTState()->SetWindowed( TRUE );
		MUTState()->SetD3DPP_Windowed( TRUE );
	}
	return MUTResetDevice();
}

extern LRESULT MUTGUIWndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam);

LRESULT CALLBACK MUTWndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam){
	static POINT mpt={0,0};
	switch( message ){
	case WM_MOUSEMOVE:
		MUTState()->SetPreMousePos(mpt);
		mpt.x = LOWORD(lParam);
		mpt.y = HIWORD(lParam);
		MUTState()->SetMousePos(mpt);
		break;
	case WM_KEYDOWN:/*
		if ( wParam == VK_F1 ){
			MUTToggleFullscreen();
		}*/
		break;
	case WM_ACTIVATE:
		switch ( LOWORD(wParam) ){
		case WA_ACTIVE:
		case WA_CLICKACTIVE:
			break;
		case WA_INACTIVE:
			break;
		}
		break;
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	}
	MUTGUIWndProc(hwnd,message,wParam,lParam);
	if ( g_OnWndProc ){
		(*g_OnWndProc)( hwnd, message, wParam, lParam );
	}
	return DefWindowProc(hwnd,message,wParam,lParam);
}

HRESULT MUTGetErrorMessage(DWORD dwErrorCode,TString& strError){
	HLOCAL hLocal = NULL;
	DWORD dwResult = FormatMessage(
		FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS,
		NULL,
		dwErrorCode,
		MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT),
		(LPTSTR)&hLocal,
		0,
		NULL);
	if (!dwResult||!hLocal){
		return E_FAIL;
	}
	strError = (LPTSTR)LocalLock(hLocal);
	LocalFree(hLocal);
	return S_OK;
}
/**
 * multithread request
 *
 */

map< HANDLE, REQUEST_BATCH > g_mapRequest;
HANDLE g_mapRequestEvent;
void MUTRequestSetup(){
	g_mapRequestEvent = CreateEvent( 0, FALSE, TRUE, 0 );
	if ( !g_mapRequestEvent )
		MUTWinErrorTrace( GetLastError() );
}

HRESULT CheckThreadRequest(){
	LPDIRECT3DDEVICE9 pDevice = MUTState()->GetDevice();
	HRESULT hr = S_OK;
	DWORD waitResult = WaitForSingleObject( g_mapRequestEvent, 0 );
	if ( waitResult != WAIT_OBJECT_0 ){
		return E_FAIL;
	}
	if ( g_mapRequest.size() ){
		for ( auto itr = g_mapRequest.begin(); itr != g_mapRequest.end(); itr++ ){
			if ( !itr->second.bReady )
				continue;
			if ( itr->second.vreqRelease.size() ){
				for ( auto i = itr->second.vreqRelease.begin(); i != itr->second.vreqRelease.end(); i++ ){
					switch ( i->type ){
					case MUTRES_VB:
						{
							LPDIRECT3DVERTEXBUFFER9 vb = ( LPDIRECT3DVERTEXBUFFER9 )i->ptr;
							vb->Release();
						}
						break;
					case MUTRES_IB:
						{
							LPDIRECT3DINDEXBUFFER9 ib = ( LPDIRECT3DINDEXBUFFER9 )i->ptr;
							ib->Release();
						}
						break;
					case MUTRES_TEXTURE:
						{
							LPDIRECT3DTEXTURE9 tex = ( LPDIRECT3DTEXTURE9 )i->ptr;
							tex->Release();
						}
						break;
					}
				}
			}
			if ( itr->second.vreqDecl.size() ){
				for ( auto i = itr->second.vreqDecl.begin(); i != itr->second.vreqDecl.end(); i++ ){
					if ( FAILED( hr = pDevice->CreateVertexDeclaration( i->elem, i->decl ) ) ){
						MUTDXErrorTrace( hr, L"request vdecl failed" );
						return E_FAIL;
					}
				}
			}
			if ( itr->second.vreqVB.size() ){
				for ( auto i = itr->second.vreqVB.begin(); i != itr->second.vreqVB.end(); i++ ){
					if ( FAILED( hr = pDevice->CreateVertexBuffer( i->dwLength, 0, i->FVF, D3DPOOL_MANAGED, i->vb, NULL ) ) ){
						MUTDXErrorTrace( hr, L"request vb failed" );
						return E_FAIL;
					}
				}
			}
			if ( itr->second.vreqIB.size() ){
				for ( auto i = itr->second.vreqIB.begin(); i != itr->second.vreqIB.end(); i++ ){
					if ( FAILED( hr = pDevice->CreateIndexBuffer( i->dwLength, 0, i->fmt, D3DPOOL_MANAGED, i->ib, NULL ) ) ){
						MUTDXErrorTrace( hr, L"request ib failed" );
						return E_FAIL;
					}
				}
			}
			if ( itr->second.vreqTexFromFile.size() ){
				for ( auto i = itr->second.vreqTexFromFile.begin(); i != itr->second.vreqTexFromFile.end(); i++ ){
					if ( FAILED( hr = D3DXCreateTextureFromFile( pDevice, i->fn, i->tex ) ) ){
						MUTDXErrorTrace( hr, L"request tex from file failed" );
						return E_FAIL;
					}
				}
			}
			if ( itr->second.vreqLock.size() ){
				for ( auto i = itr->second.vreqLock.begin(); i != itr->second.vreqLock.end(); i++ ){
					switch( i->type ){
					case MUTRES_VB:
						{
							LPDIRECT3DVERTEXBUFFER9 vb = ( LPDIRECT3DVERTEXBUFFER9 )i->ptr;
							vb->Lock( 0, 0, i->out, i->flag );
						}
						break;
					case MUTRES_IB:
						{
							LPDIRECT3DINDEXBUFFER9 ib = ( LPDIRECT3DINDEXBUFFER9 )i->ptr;
							ib->Lock( 0, 0, i->out, i->flag );
						}
						break;
					case MUTRES_TEXTURE:
						{
							LPDIRECT3DTEXTURE9 tex = ( LPDIRECT3DTEXTURE9 )i->ptr;
							tex->LockRect( 0, (D3DLOCKED_RECT*)*i->out, 0, i->flag );
						}
						break;
					}
				}
			}
			if ( itr->second.vreqUnlock.size() ){
				for ( auto i = itr->second.vreqUnlock.begin(); i != itr->second.vreqUnlock.end(); i++ ){
					switch( i->type ){
					case MUTRES_VB:
						{
							LPDIRECT3DVERTEXBUFFER9 vb = ( LPDIRECT3DVERTEXBUFFER9 )i->ptr;
							vb->Unlock();
						}
						break;
					case MUTRES_IB:
						{
							LPDIRECT3DINDEXBUFFER9 ib = ( LPDIRECT3DINDEXBUFFER9 )i->ptr;
							ib->Unlock();
						}
						break;
					case MUTRES_TEXTURE:
						{
							LPDIRECT3DTEXTURE9 tex = ( LPDIRECT3DTEXTURE9 )i->ptr;
							tex->UnlockRect( 0 );
						}
						break;
					}
				}
			}
			itr->second.bDone = TRUE;
		}

		auto itr = g_mapRequest.begin();
		while ( itr != g_mapRequest.end() ){
			if ( itr->second.bDone ){
				SetEvent( itr->first);
				itr = g_mapRequest.erase( itr );
			}else
				itr++;
		}
	}
	SetEvent( g_mapRequestEvent );
	return S_OK;
}
void MUTRequestBegin( HANDLE hEvent ){
	WaitForSingleObject( g_mapRequestEvent, INFINITE );
	g_mapRequest[ hEvent ].bReady = FALSE;
	g_mapRequest[ hEvent ].bDone = FALSE;
}
void MUTRequestEnd( HANDLE hEvent ){
	g_mapRequest[ hEvent ].bReady = TRUE;
	SetEvent( g_mapRequestEvent );
	WaitForSingleObject( hEvent, INFINITE );
}
void MUTRequestRelease( HANDLE hEvent, MUTRES_TYPE type, LPVOID ptr ){
	REQUEST_RELEASE reqRelease;
	reqRelease.type = type;
	reqRelease.ptr = ptr;
	g_mapRequest[ hEvent ].vreqRelease.push_back( reqRelease );
}
void MUTRequestVertexDeclaration( HANDLE hEvent, LPD3DVERTEXELEMENT9 elem, LPDIRECT3DVERTEXDECLARATION9& decl ){
	REQUEST_VERTEXDECLARATION reqDecl;
	reqDecl.elem = elem;
	reqDecl.decl = &decl;
	g_mapRequest[ hEvent ].vreqDecl.push_back( reqDecl );
}
void MUTRequestVertexBuffer( HANDLE hEvent, DWORD dwLength, DWORD FVF, LPDIRECT3DVERTEXBUFFER9& vb ){
	REQUEST_VERTEXBUFFER reqVB;
	reqVB.dwLength = dwLength;
	reqVB.FVF = FVF;
	reqVB.vb  = &vb;
	g_mapRequest[ hEvent ].vreqVB.push_back( reqVB );
}
void MUTRequestIndexBuffer( HANDLE hEvent, DWORD dwLength, D3DFORMAT fmt, LPDIRECT3DINDEXBUFFER9& ib ){
	REQUEST_INDEXBUFFER reqIB;
	reqIB.dwLength = dwLength;
	reqIB.fmt = fmt;
	reqIB.ib = &ib;
	g_mapRequest[ hEvent ].vreqIB.push_back( reqIB );
}
void MUTRequestTextureFromFile( HANDLE hEvent, LPCWSTR fn, LPDIRECT3DTEXTURE9& tex ){
	REQUEST_TEXTUREFROMFILE reqtex;
	reqtex.fn = fn;
	reqtex.tex = &tex;
	g_mapRequest[ hEvent ].vreqTexFromFile.push_back( reqtex );
}
void MUTRequestLock( HANDLE hEvent, MUTRES_TYPE type, LPVOID ptr, DWORD flag, LPVOID& out ){
	REQUEST_LOCK reqlock;
	reqlock.type = type;
	reqlock.ptr = ptr;
	reqlock.flag = flag;
	reqlock.out = &out;
	g_mapRequest[ hEvent ].vreqLock.push_back( reqlock );
}
void MUTRequestUnlock( HANDLE hEvent, MUTRES_TYPE type, LPVOID ptr ){
	REQUEST_UNLOCK requnlock;
	requnlock.type = type;
	requnlock.ptr = ptr;
	g_mapRequest[ hEvent ].vreqUnlock.push_back( requnlock );
}

void DrawFullScreenRect( LPDIRECT3DDEVICE9 pDevice ){
	LPDIRECT3DSURFACE9 pTarget = 0;
	D3DSURFACE_DESC desc;
	pDevice->GetRenderTarget( 0, &pTarget );
	pTarget->GetDesc( &desc );
	pTarget->Release();
	FULLSCREENVERTEX vertices[4] = {
		{ D3DXVECTOR4( 0, desc.Height, 1, 1 ), D3DXVECTOR2(0,1) },
		{ D3DXVECTOR4( 0, 0, 1, 1 ), D3DXVECTOR2(0,0) },
		{ D3DXVECTOR4( desc.Width, desc.Height, 1, 1 ), D3DXVECTOR2(1,1) },
		{ D3DXVECTOR4( desc.Width, 0, 1, 1 ), D3DXVECTOR2(1,0) }
	};

	WORD indices[4] = { 0,1,2,3 };
	pDevice->SetFVF( D3DFVF_XYZRHW|D3DFVF_TEX1 );
	pDevice->DrawIndexedPrimitiveUP( D3DPT_TRIANGLESTRIP, 0, 4, 2, indices, D3DFMT_INDEX16, vertices, sizeof( FULLSCREENVERTEX ) );
	
}